-
Notifications
You must be signed in to change notification settings - Fork 6k
future: support engine create shell with aysnc mode #18047
future: support engine create shell with aysnc mode #18047
Conversation
0932d55
to
fc4445d
Compare
shell/common/shell.h
Outdated
static void Create( | ||
ShellCreateCallback callback, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same feedback I had on the other issue. The engine assumes the shell is initialized synchronously everywhere. I don't support any change that relies on callbacks instead of std::future's to guarantee safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi,I've tried to use promise/future before, but there are serious problesm in this case
- first , it may disturb the sequence ( shell create )
- There are many thread switches in the creation sequence when create shell,if
future.get()
called(block thread) on platform/ui/gpu/io thead,may disturb the timing
- There are many thread switches in the creation sequence when create shell,if
- second,will bring burden to the user
- android/ios must called
future.get()
on newThread(can't be io/ui/gpu/platform)
- android/ios must called
- finally ,May be cause deadlock if user called
future.get()
on platformthread 。Because we need post a task to platformthread to excuteshell.setup
after engine created
//demo code on PlatformThread
auto future = Shell::Create(...);
future.get();
//shell.cc (after review and modify)
//now on ui thread
auto engine_ref = std::make_unique<Engine>(... );
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(), fml::MakeCopyable([]() mutable{
//dead lock (setupmust called on platformthread)
shell->Setup(...);
}));
- For safety, static method
Create
will guarantee shell full initialized , the callback will be called after all init sequence/setp excuted. So,In my understanding,the shell should be safe.
Looking forward to your reply
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate your consideration and thought put into this =)
it may disturb the sequence
What you are referring to is the synchronization that the future guarantees. The alternative is a race condition that may crash sometimes. The worst kind of bug to track down.
will bring burden to the user
This is not really a burden.
May be cause deadlock if user called future.get() on platformthread
This is a risk but it doesn't go away if you use callbacks instead of futures. If you are in a position where you would get a deadlock with futures, you will have an uninitialized variable and will have to abort the operation somehow, or you erroneously went down a code path prematurely. You can mimic the checking of an unfinished future by using wait_for
.
I think your aversion to using futures stems from unfamiliarity and not a solid technical basis. All the problems you have with futures exist with callbacks, they are just harder to verify and thus easier to convince yourself they are correct, more fragile, and harder to debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@liyuqian @lucky-chen I created some sample code that duplicates the deadlock predicament to play around with: https://gist.github.com/gaaclarke/84caf13aa0da0a7a2198247e2e6c8cd0
I think the easiest solution is to remove the dispatch to the platform thread. Just do those calculations up front on the platform thread or if the calculations depend on previous results create a partial result like this:
class ShellHolder {
private:
unique_ptr<Shell> _shell;
std::future<PartialShell> _partial_shell;
public:
Shell* get() {
if (_shell) {
return _shell;
}
_shell = CompleteShell(_partial_shell.get());
return _shell;
}
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gaaclarke Thanks reply. Other suggestions are very good, but I still hold different views on callback/promise。
Suppose we have 2 method
- promise/future
std::future<std::unique_ptr<Shell>> Create(...)
- callback
void Create( callback)
when init on android /ios
use future
//on platformThread
void init(){
auto future = create(...);
//usage 1
future.get(); // bad code, The cost of blocking the main thread is very expensive on android/ios .
//maybe disturb the sequence of createshell ,such as deadlock
//usage2 check status
if(isFutureReady(future)){
//balaba
}else{
// called launchFlutter() on some point (such as start flutterActivity)
}
}
void launchFlutter(){
future.get(); // if ready, fine . else block (bad)
//balabala
}
bool isFutureReady(std::future& f){...}
use callback
but with callback , very simple and users have almost no burden
void init(){
Shell::Create([]( std::unique_ptr<Shell> shell ){
//on platformthread
// no burder , no deadlock
// will non't disturb the sequenc of createshell
shell_ = std:move(shell);
//balabala
})
}
As api user, only care about result output. Shouldn't to deal with some additional state. These states should be handled by api self.
in this case , Either return the shell(full init) or don't return。Return future is not a good way in this scene because the future carries intermediate states related to internal logic of api,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gaaclarke @lucky-chen : I believe the problem of C++ std::future
here is the lack of std::future::then
. (We all get used to Future.then
in Dart and I'm surprised that C++ doesn't have the counterpart.) The example above would be easy with std::future
if the current experimental "then" can be used:
auto future = create(...);
future.then(callback);
Without then
, I believe the best we can do now with future
is std::async
:
auto future = create(...);
std::async(std::launch::async, [...](){
future.get();
callback(...);
});
It ensures that the waiting happens on another thread (other than the platform thread) so it will not block or dead lock.
Note that the current callback implementation in this PR may also have problem: it's unclear what thread will execute the callback. Currently the UI thread executes it and avoids block and dead lock. But that violates calling WealkPtr
in the same thread. If we correctly calls the Shell::Setup
and subsequently the callback in the platform thread, then we end up with blocking and dead lock again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@liyuqian @gaaclarke According to the suggestion of review, made some changes,may be better
new api is future<ShellHolder> createShellHolder(...)
usage 1 (sync mode):
auto shell_holder_future = createShellHolder();
auto shell_holder = shell_holder_future.get();
auto shell = shell_holder.makeShell();
usage 2 (async mode)
auto shell_holder_future = createShellHolder();
std::async(std::launch::async,[](){
//wait engine create end
auto shell_holder = shell_holder_future.get();
//makeShell method guarantee called setUp on platformThread
auto shell = shell_holder.makeShell();
//...
});
for shellHolder.makeShell
fml::AutoResetWaitableEvent latch;
std::unique_ptr<Shell> shell_res;
fml::TaskRunner::RunNowOrPostTask(PlatformTaskRunner,[]() {
shell_->Setup()
shell_res = std::move(this->shell_);
latch.Signal();
);
latch.Wait();
return shell_res;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to Aaron's suggestion to directly let CreateShellOnPlatformThread
return a future instead of taking a callback.
Other than that, the biggest issue seems to be the calling of WeakPtrFactory::GetWeakPtr
. Although the code document says that it's only safe to be called from the thread that the WeakPtr
is created, it seems that none of our unit tests are failing if that's not the case. @chinmaygarde : is there any reason that we haven't already added CheckThreadSafety();
to WeakPtrFactory::GetWeakPtr
?
I wonder if this PR and its following Android PR could fix flutter/flutter#40563 ? For now, we're blocking the Android's main thread during engine initialization and that could explain |
bc69130
to
f4658b3
Compare
@lucky-chen Looks like you are going to have to merge master in order to get the presubmits green. |
+1 to @gaaclarke 's suggestion. Here's the command that I use to rebase against tip-of-tree master branch engine: |
1034ed9
to
881036e
Compare
@liyuqian update to master,but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lucky-chen I couldn't figure out why those tests fail either... I'd usually rebase again if I encountered such mysterious failures. I also left some partial comments as I haven't had time to review the full PR yet. Hopefully they're still helpful :)
881036e
to
8506bb5
Compare
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
1 similar comment
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. ℹ️ Googlers: Go here for more info. |
8506bb5
to
e46e18a
Compare
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
1 similar comment
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are getting close. I still have concerns about deadlock mentioned in the comments and I'd like to get ride of is_consumed_
also noted in the feedback.
This PR is very tricky to get right and not the highest priority so thanks for your patience as we get this right.
2adc992
to
74c0c2c
Compare
Dude, @lucky-chen, make sure you hit the "re-request review" after you've done work like this. I don't get notifications otherwise. I want to see this stuff! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is good now. This is pretty mad but it looks good. @liyuqian can you verify it for me please, it's complicated enough that it warrants 2 people to take a close look.
use promise/future op update doc
74c0c2c
to
3800e23
Compare
I rebased this onto master to shake out CI errors with windows. |
@lucky-chen The failure with windows builds is legitimate:
|
I reran the outstanding windows problem to see if it was a flake, it doesn't appear to be:
This appears to be a memory error which is going to be a pain to track down. |
unref_queue_future.get(), // | ||
snapshot_delegate_future.get() // | ||
)); | ||
// wait params(io、gpu task end) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
、
should be replaced with ,
std::unique_ptr<ShellCreateParams> params) { | ||
PerformInitializationTasks(params->settings); | ||
PersistentCache::SetCacheSkSL(params->settings.cache_sksl); | ||
TRACE_EVENT0("flutter", "Shell::Create"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put TRACE_EVENT0
as the first line of the function and change "Shell::Create"
to "Shell::CreateShellHolde"
@@ -283,42 +300,76 @@ std::unique_ptr<Shell> Shell::Create( | |||
const Shell::CreateCallback<PlatformView>& on_create_platform_view, | |||
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer, | |||
DartVMRef vm) { | |||
auto shell_holder_future = Shell::InitShellEnv( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put TRACE_EVENT0("flutter", "Shell::Create");
as the first line of this function.
return shell; | ||
} | ||
|
||
std::future<std::unique_ptr<Shell::ShellHolder>> Shell::InitShellEnv( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: InitializeShellHolder
or CreateShellHolder
might be a better name than InitShellEnv
as we usually don't encourage abbreviations like Env
.
} | ||
|
||
fml::AutoResetWaitableEvent latch; | ||
std::unique_ptr<Shell> shell; | ||
// fml::AutoResetWaitableEvent latch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove // fml::AutoResetWaitableEvent latch;
fml::AutoResetWaitableEvent latch; | ||
std::unique_ptr<Shell> shell; | ||
// fml::AutoResetWaitableEvent latch; | ||
std::promise<std::unique_ptr<Shell::ShellHolder>> shell_holder_promise; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can std::promise<std::unique_ptr<Shell::ShellHolder>> shell_holder_promise;
be put before line 340 so we can save line 343?
@@ -432,6 +493,15 @@ class Shell final : public PlatformView::Delegate, | |||
const Shell::CreateCallback<PlatformView>& on_create_platform_view, | |||
const Shell::CreateCallback<Rasterizer>& on_create_rasterizer); | |||
|
|||
static std::future<std::unique_ptr<Shell::ShellHolder>> InitShellEnv( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document this function.
@@ -423,7 +483,8 @@ class Shell final : public PlatformView::Delegate, | |||
|
|||
Shell(DartVMRef vm, TaskRunners task_runners, Settings settings); | |||
|
|||
static std::unique_ptr<Shell> CreateShellOnPlatformThread( | |||
static void CreateShellOnPlatformThread( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document this function especially on how the new shell_holder_promise
parameter should be given and used later.
I think this PR is quite a obscurity. the |
@lucky-chen This PR is getting a bit stale. @gaaclarke's and @liyuqian's feedback hasn't been taken into account. A more focused PR will probably be easier to review and land. |
this is a split PR, supprot create shell with aysnc mode , you can see full contex on #17192
core modify method is
CreateShellOnPlatformThread
. Other code are@chinmaygarde